package com.wonders.frame.aspect;

import com.wonders.frame.annotation.Changes;
import com.wonders.frame.common.Enum.ChangeType;
import com.wonders.frame.common.util.SpringContextUtil;
import com.wonders.frame.service.AuthChangesService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;

import javax.persistence.Table;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

@Aspect
@Component
public class ChangesAspect {
    private Logger log = LoggerFactory.getLogger(ChangesAspect.class);

    @Autowired
    private AuthChangesService authChangesService;

    @Pointcut("@annotation(com.wonders.frame.annotation.Changes)")
    public void changesPointCut() {
    }

    @SuppressWarnings({"unchecked", "ConstantConditions"})
    @Around(value = "changesPointCut()&&@annotation(changes)", argNames = "point,changes")
    public Object around(ProceedingJoinPoint point, Changes changes) throws Throwable {
        Object result = null;
        //获取注解传递的参数
//        Class<?> cClass = changes.type();
        Class<?> cClass;//操作的具体实体类
        Class<?> repoClass;//获取repository
        ChangeType changeType;//异动类型
        try {
            cClass = (Class<?>) ((ParameterizedType) point.getTarget().
                    getClass().getGenericSuperclass()).getActualTypeArguments()[0];
            repoClass = changes.repo();
            changeType = changes.changeType();
        } catch (Exception e) {
            log.info("通过泛型获取实体类失败");
            e.printStackTrace();
            return null;
        }

        //获取切面方法的参数列表
        Object[] args = point.getArgs();

        //获取操作的数据库的表名
        String changeObj = cClass.getAnnotation(Table.class).name();

        for (Object o : args) {
            //判断传入的参数是否为该类的对象
            if (cClass == o.getClass()) {
                //获取repository
                JpaRepository repo = null;
                if (SpringContextUtil.getApplicationContext().containsBean(repoClass.getSimpleName())) {
                    repo = (JpaRepository) SpringContextUtil.getApplicationContext().getBean(repoClass);
                }
                //获取getId方法
                Method methodGetId;
                try {
                    methodGetId = o.getClass().getMethod("getId");
                    switch (changeType) {
                        case SAVE:
                            //判断是新增还是更新
                            if (repo.findById(methodGetId.invoke(o)).isPresent()) {
                                result = update(point, cClass, methodGetId, repo, o, changeObj);
                            } else {
                                result = others(changeObj, ChangeType.ADD, point, methodGetId);
                            }
                            break;
                        case UPDATE:
                            if (repo.findById(methodGetId.invoke(o)).isPresent()) {
                                result = update(point, cClass, methodGetId, repo, o, changeObj);
                            }
                            break;
                        case ADD:
                            result = others(changeObj, changeType, point, methodGetId);
                            break;
                        /*case DELETE:
                            result = notUpdate(changeObj, changeType, point, methodGetId);
                            break;*/
                        case SEARCH:
                            result = others(changeObj, changeType, point, methodGetId);
                            break;
                    }
                } catch (NoSuchMethodException e) {
                    log.info(cClass.getSimpleName() + "类没有getId方法");
                    e.printStackTrace();
                    return null;
                } catch (SecurityException e) {
                    e.printStackTrace();
                    return null;
                }
            } else if (ChangeType.DELETE.equals(changeType)) {
                /*//获取CoreCurdService的具体类(即实际操作的实体类)
                cClass = (Class<?>) ((ParameterizedType) point.getTarget().
                        getClass().getGenericSuperclass()).getActualTypeArguments()[0];
                changeObj = cClass.getAnnotation(Table.class).name();*/
                for (Parameter p : ((MethodSignature) point.getSignature()).getMethod().getParameters()) {
                    //根据id(参数名字只能是id或ids)的数量执行异动 ids可以适配string[]和List
                    if ("id".equals(p.getName()) && p.getType() == String.class) {
                        result = delete(changeObj, changeType, point, (String) o);
                    } else if ("ids".equals(p.getName()) && p.getType() == String[].class) {
                        for (String id : (String[]) o) {
                            result = delete(changeObj, changeType, point, id);
                        }
                    } else if ("ids".equals(p.getName()) && p.getType() == List.class) {
                        Type[] types = ((ParameterizedType) p.getType().getGenericSuperclass()).getActualTypeArguments();
                        if (types.length == 1 && types[0] == String.class) {
                            for (String id : (String[]) o) {
                                result = delete(changeObj, changeType, point, id);
                            }
                        }
                        try {
                            for (Object id : (List) o) {
                                result = delete(changeObj, changeType, point, (String) id);
                            }
                        } catch (Throwable throwable) {
                            log.info("根据List删除失败");
                            throwable.printStackTrace();
                            return null;
                        }
                    }
                }
            }
        }
        return result;
    }

    @SuppressWarnings({"unchecked", "OptionalGetWithoutIsPresent"})
    private Object update(ProceedingJoinPoint point, Class cClass, Method methodGetId, JpaRepository repo,
                          Object o, String changeObj) throws Throwable {
        Object result;
        //获取修改前对象
        Object oldObj = repo.findById(methodGetId.invoke(o)).get();
        //获取修改对象id
        String changeId = (String) methodGetId.invoke(o);
        //获取字段映射方法
        Method methodGetColMap;
        //字段映射Map
        Map<String, String> colMap;
        try {
            methodGetColMap = o.getClass().getMethod("getColMap");
            colMap = (Map<String, String>) methodGetColMap.invoke(o);
            authChangesService.setChanges(null, colMap, oldObj, o,
                    changeId, changeObj, ChangeType.UPDATE, null);
        } catch (NoSuchMethodException e) {
            log.info(cClass.getSimpleName() + "类没有getColMap获得字段映射方法");
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        //执行切面的方法
        result = point.proceed();
        return result;
    }

    private Object delete(String changeObj, ChangeType changeType, ProceedingJoinPoint point,
                          String changeId) throws Throwable {
        Object result = point.proceed();
        authChangesService.setChanges(null, changeId, changeObj, changeType);
        return result;
    }

    private Object others(String changeObj, ChangeType changeType, ProceedingJoinPoint point,
                          Method methodGetId) throws Throwable {
        Object result = point.proceed();
        //获取对象id
        String changeId = (String) methodGetId.invoke(result);
        authChangesService.setChanges(null, changeId, changeObj, changeType);
        return result;
    }
}
